;;;
;;; mp3tunes_fuse.scm - mp3tunes filesystem
;;;
;;;   Copyright (c) 2008 Teruaki Gemma <teruakigemma@gmail.com>
;;;
;;;   Redistribution and use in source and binary forms, with or without
;;;   modification, are permitted provided that the following conditions
;;;   are met:
;;;
;;;   1. Redistributions of source code must retain the above copyright
;;;      notice, this list of conditions and the following disclaimer.
;;;
;;;   2. Redistributions in binary form must reproduce the above copyright
;;;      notice, this list of conditions and the following disclaimer in the
;;;      documentation and/or other materials provided with the distribution.
;;;
;;;   3. Neither the name of the authors nor the names of its contributors
;;;      may be used to endorse or promote products derived from this
;;;      software without specific prior written permission.
;;;
;;;   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
;;;   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
;;;   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
;;;   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
;;;   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
;;;   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
;;;   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
;;;   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
;;;   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;;;   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;;;   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
;;;
;;;  $Id$
;;;

(use srfi-1)
(use file.util)
(use rfc.http)
(use rfc.uri)
(use sxml.ssax)
(use sxml.sxpath)
(use sxml.tools)
(use sxml.serializer)
(use gauche.parseopt)
(use gauche.uvector)
(use fuse)

(load "/home/teruaki/garakuta-okiba/wnix/adhoc.scm")

(define (artistName->artistId name)
  (call-with-input-file (string-append "./root/artist/" name ".xml")
    (lambda (in)
      ((if-car-sxpath "//artistId/text()") (ssax:xml->sxml in '())))))

(define (albumTitle->albumId title)
  (call-with-input-file (string-append "./root/album/" title ".xml")
    (lambda (in)
      ((if-car-sxpath "//albumId/text()") (ssax:xml->sxml in '())))))

(define (playlistTitle->playlistId title)
  (call-with-input-file (string-append "./root/playlist/" title ".xml")
    (lambda (in)
      ((if-car-sxpath "//playlistId/text()") (ssax:xml->sxml in '())))))

(define (my-getattr path)
  (cond
   ((adhoc-shell-cmd path) 
    => (lambda (shellcmd)
	 (xml-writer "./com/1"
		     (call-with-input-process shellcmd (cut ssax:xml->sxml <> '()))
		     #f)
	 (sys-stat "./com/1")))
   (else (sys-stat (string-append "./root" path)))))

(define (my-readdir path fuse-info)
  (define (empty-directory? path)
    (equal?  '("." "..") (directory-list path)))

  (cond
   ;;/
   ((string=? "/" path)
    (unless (file-exists? "./root/user.xml")
      (xml-writer "./root/user.xml" ((sxpath "//mp3tunes") (mp3tunes-account-data)) "../xsl/user.xsl"))
    (for-each (lambda (dirname)
		(let1 path (string-append "./root/" dirname)
		  (unless (file-is-directory? path)
		    (sys-mkdir path #o755))))
	      '("album" "album_token" "artist" "artist_token" "album_artist" "track" "track_album" "track_artist" "track_playlist" "track_token" "playlist")))
   
   ;;/artist
   ((and (string=? "/artist" path)
	 (empty-directory? "./root/artist"))
    (every (lambda (item)
	     (and-let* ((artistName ((if-car-sxpath "/artistName/text()") item)))
	       (xml-writer (string-append "./root/artist/" artistName ".xml")
			   item
			   "../../xsl/artist.xsl")))
	   ((sxpath "//item") (mp3tunes-locker-data 'artist))))

   ;;/album
   ((and (string=? "/album" path)
	 (empty-directory? "./root/album"))
    (every (lambda (item)
	     (and-let* ((albumTitle ((if-car-sxpath "/albumTitle/text()") item)))
	       (xml-writer (string-append "./root/album/" albumTitle ".xml")
			   item
			   "../../xsl/album.xsl")))
	   ((sxpath "//item") (mp3tunes-locker-data 'album))))

   ;;/track
   ((and (string=? "/track" path)
	 (empty-directory? "./root/track"))
    (every (lambda (item)
	     (and-let* ((trackTitle ((if-car-sxpath "/trackTitle/text()") item)))
	       (xml-writer (string-append "./root/track/" trackTitle ".xml")
			   (write-partner_token item)
			   "../../xsl/track.xsl")))
	   ((sxpath "//item") (mp3tunes-locker-data 'track))))

   ;;/playlist
   ((and (string=? "/playlist" path)
	 (empty-directory? "./root/playlist"))
    (every (lambda (item)
	     (and-let* ((playlistTitle ((if-car-sxpath "/playlistTitle/text()") item)))
	       (xml-writer (string-append "./root/playlist/" playlistTitle ".xml")
			   item
			   "../../xsl/playlist.xsl")))
	   ((sxpath "//item") (mp3tunes-locker-data 'playlist))))

   ;;/album_artist
   ((and (string=? "/album_artist" path)
	 (empty-directory? "./root/album_artist"))
    (every (lambda (artistName)
	     (sys-mkdir (string-append "./root/album_artist/" artistName) #o755))
	   (map (lambda (x)
		  ((#/^\.\/root\/artist\/(.*)\.xml$/ x) 1))
		;; TODO should preparely load ./root/artist/
		(glob "./root/artist/*.xml"))))
   ;;/album_artist/*
   ((and (string=? "/album_artist" (sys-dirname path))
	 (empty-directory? (string-append "./root" path)))
    (let* ((artistName (sys-basename path))
	   (artistId   (artistName->artistId artistName)))
      (every (lambda (item)
	       (and-let* ((albumTitle ((if-car-sxpath "/albumTitle/text()") item)))
		 (xml-writer (string-append "./root/album_artist/" artistName "/" albumTitle ".xml")
			     item
			     "../../../xsl/album.xsl")))
	     ((sxpath "//item") (mp3tunes-locker-data 'album :artist_id artistId)))))
   
   ;;/artist_token
   ((and (string=? "/artist_token" path)
	 (empty-directory? "./root/artist_token"))
    (every (lambda (token)
	     (sys-mkdir (string-append "./root/artist_token/" token) #o755))
	   ((sxpath "//item/token/text()") (mp3tunes-locker-data 'artist_token))))
   ;;/artist_token/*
   ((and (string=? "/artist_token" (sys-dirname path))
	 (empty-directory? (string-append "./root" path)))
    (every (lambda (item)
	     (and-let* ((artistName ((if-car-sxpath "/artistName/text()") item)))
	       (xml-writer (string-append "./root/artist_token/" (sys-basename path) "/" artistName ".xml")
			   item
			   "../../../xsl/artist.xsl")))
	   ((sxpath "//item") (mp3tunes-locker-data 'artist :token (sys-basename path)))))

   ;;/album_token
   ((and (string=? "/album_token" path)
	 (empty-directory? "./root/album_token"))
    (every (lambda (token)
	     (sys-mkdir (string-append "./root/album_token/" token) #o755))
	   ((sxpath "//item/token/text()") (mp3tunes-locker-data 'album_token))))
   ;;/album_token/*
   ((and (string=? "/album_token" (sys-dirname path))
	 (empty-directory? (string-append "./root" path)))
    (every (lambda (item)
	     (and-let* ((albumTitle ((if-car-sxpath "/albumTitle/text()") item)))
	       (xml-writer (string-append "./root/album_token/" (sys-basename path) "/" albumTitle ".xml")
			   item
			   "../../../xsl/album.xsl")))
	   ((sxpath "//item") (mp3tunes-locker-data 'album :token (sys-basename path)))))

   ;;/track_token
   ((and (string=? "/track_token" path)
	 (empty-directory? "./root/track_token"))
    (every (lambda (token)
	     (sys-mkdir (string-append "./root/track_token/" token) #o755))
	   ((sxpath "//item/token/text()") (mp3tunes-locker-data 'track_token))))
   ;;/track_token/*
   ((and (string=? "/track_token" (sys-dirname path))
	 (empty-directory? (string-append "./root" path)))
    (every (lambda (item)
	     (and-let* ((trackTitle ((if-car-sxpath "/trackTitle/text()") item)))
	       (xml-writer (string-append "./root/track_token/" (sys-basename path) "/" trackTitle ".xml")
			   (write-partner_token item)
			   "../../../xsl/track.xsl")))
	   ((sxpath "//item") (mp3tunes-locker-data 'track :token (sys-basename path)))))

   ;;/track_playlist
   ((and (string=? "/track_playlist" path)
	 (empty-directory? "./root/track_playlist"))
    (every (lambda (playlistTitle)
	     (sys-mkdir (string-append "./root/track_playlist/" playlistTitle) #o755))
	   (map (lambda (x)
		  ((#/^\.\/root\/playlist\/(.*)\.xml$/ x) 1))
		(glob "./root/playlist/*.xml"))))
   ;;/track_playlist/*
   ((and (string=? "/track_playlist" (sys-dirname path))
	 (empty-directory? (string-append "./root" path)))
    (let* ((playlistTitle (sys-basename path))
	   (playlistId    (playlistTitle->playlistId playlistTitle)))
      (every (lambda (item)
	       (and-let* ((trackTitle ((if-car-sxpath "/trackTitle/text()") item)))
		 (xml-writer (string-append "./root/track_playlist/" playlistTitle "/" trackTitle ".xml")
			     (write-partner_token item)
			     "../../../xsl/track.xsl")))
	     ((sxpath "//item") (mp3tunes-locker-data 'track :playlist_id playlistId)))))

   ;;/track_album
   ((and (string=? "/track_album" path)
	 (empty-directory? "./root/track_album"))
    (every (lambda (albumTitle)
	     (sys-mkdir (string-append "./root/track_album/" albumTitle) #o755))
	   (map (lambda (x)
		  ((#/^\.\/root\/album\/(.*)\.xml$/ x) 1))
		(glob "./root/album/*.xml"))))
   ;;/track_album/*
   ((and (string=? "/track_album" (sys-dirname path))
	 (empty-directory? (string-append "./root" path)))
    (let* ((albumTitle (sys-basename path))
	   (albumId    (albumTitle->albumId albumTitle)))
      (every (lambda (item)
	       (and-let* ((trackTitle ((if-car-sxpath "/trackTitle/text()") item)))
		 (xml-writer (string-append "./root/track_album/" albumTitle "/" trackTitle ".xml")
			     (write-partner_token item)
			     "../../../xsl/track.xsl")))
	     ((sxpath "//item") (mp3tunes-locker-data 'track :album_id albumId)))))

   ;;/track_artist
   ((and (string=? "/track_artist" path)
	 (empty-directory? "./root/track_artist"))
    (every (lambda (artistName)
	     (sys-mkdir (string-append "./root/track_artist/" artistName) #o755))
	   (map (lambda (x)
		  ((#/^\.\/root\/artist\/(.*)\.xml$/ x) 1))
		(glob "./root/artist/*.xml"))))
   ;;/track_artist/*
   ((and (string=? "/track_artist" (sys-dirname path))
	 (empty-directory? (string-append "./root" path)))
    (let* ((artistName (sys-basename path))
	   (artistId   (artistName->artistId artistName)))
      (every (lambda (item)
	       (and-let* ((trackTitle ((if-car-sxpath "/trackTitle/text()") item)))
		 (xml-writer (string-append "./root/track_artist/" artistName "/" trackTitle ".xml")
			     (write-partner_token item)
			     "../../../xsl/track.xsl")))
	     ((sxpath "//item") (mp3tunes-locker-data 'track :artist_id artistId)))))
   )
  (sys-readdir (string-append "./root" path)))

(define (my-open path fuse-info)
  (cond
   ((adhoc-shell-cmd path)
    0)
   ((file-exists? (string-append "./root" path))
    0)
   (else (- ENOENT))))

(define (my-read path out size offset fuse-info)
  (define (copy-port-block src dest size offset)
    (define vec (make-u8vector size))
    (port-seek src offset)
    (let1 r (read-block! vec src)
      (and (not (eof-object? r))
	   (write-block vec dest)
	   r)))
  (cond
   ((adhoc-shell-cmd path)
    (call-with-input-file "./com/1" (cut copy-port-block <> out size offset)))
   (else
    (call-with-input-file (string-append "./root" path) (cut copy-port-block <> out size offset)))))

(define (my-unlink path)
  (sys-unlink (string-append "./root" path)))

(define (main args)
  (load "./mp3tunes.scm")
  (load "~/my_secret.scm")
  (when (or (string=? username "")
	    (string=? password "")
	    (string=? partner_token ""))
    (print "Set username,password and partner_token.")
    (exit 1))
  (when (string=? "" login-sxml-session-id)
    (set! login-sxml (mp3tunes-login))
    (unless (string=? "1" ((car-sxpath "//status/text()") login-sxml))
      (print "mp3tunes-login error:" login-sxml)
      (exit 1))
    (set! login-sxml-session-id ((if-car-sxpath "//session_id/text()") login-sxml)))
  (start-fuse (car (last-pair args))
	      :args args
	      :multithreaded #f
	      :getattr  my-getattr
	      ;;  :readlink sys-readlink
	      ;;  :mkdir    sys-mkdir
	      ;;  :symlink  sys-symlink
	      :unlink   my-unlink
	      ;;  :rmdir    sys-rmdir
	      ;;  :rename   sys-rename
	      ;;  :link     sys-link
	      ;;  :chmod    sys-chmod
	      ;;  :chown    sys-chown
	      ;;  :truncate sys-truncate
	      ;;  :utimens  sys-utime
	      ;;  :statfs   sys-statvfs
	      :readdir  my-readdir
	      :open     my-open
	      :read     my-read
	      ;;  :write    fusexmp-write
	      ))

